#include <errno.h>
#include <unistd.h>

#include "DefaultCallback.h"

#include "Parser.h"

#include "debug.h"

size_t const readBufferSize = 65536;
cbsocket::DefaultCallback::DefaultCallback(int fd, Parser *parser):
AbstractCallback(fd),
readBuffer(readBufferSize),
buffers(),
parser(parser),
pos(0),
writable(true),
failed(false),
eof(false)
{}

void cbsocket::DefaultCallback::dataReady()
{
    ssize_t nread = 0;
    while(nread = ::read(fd, readBuffer.data(), readBufferSize), nread > 0)
        parser->processData(readBuffer.data(), nread);
    debug("Read %i bytes.\n", (int)nread);
    if(nread == 0)
        eof = true;
    else if(errno != EAGAIN && errno != EWOULDBLOCK)
        failed = true;
}

void cbsocket::DefaultCallback::writeReady()
{
    writable = true;
    while(writable && !buffers.empty() && !failed)
    {
        std::vector<char> const& current_buffer = buffers.front();
        void const* data = current_buffer.data() + pos;
        size_t size = current_buffer.size() - pos;
        size_t written = tryWrite(data, size);
        if(written == size)
        {
            pos = 0;
            buffers.pop_front();
        }
        else
            pos += written;
    }
}

void cbsocket::DefaultCallback::error()
{
    debug("Error.\n");
    failed = true;
}

void cbsocket::DefaultCallback::hangUp()
{
    debug("Hang up.\n");
}

bool cbsocket::DefaultCallback::needCleanUp()
{
    return eof || failed;
}

bool cbsocket::DefaultCallback::write(const void* data, size_t size)
{
    if(writable && !failed)
    {
        size_t written = tryWrite(data, size);
        if(written < size)
            pushBuffer(data + written, size - written);
    }
    else
        pushBuffer(data, size);
    return failed;
}

size_t cbsocket::DefaultCallback::tryWrite(const void* data, size_t size)
{
    size_t written = 0;
    while(size > 0 && writable && !failed)
    {
        ssize_t nwrite = ::write(fd, data, size);
        if(nwrite >= 0)
        {
            written += nwrite;
            data += nwrite;
            size -= nwrite;
        }
        else if(errno == EAGAIN || errno == EWOULDBLOCK)
            writable = false;
        else
            failed = true;
    }
    return written;
}

void cbsocket::DefaultCallback::pushBuffer(const void* data_, size_t size)
{
    char const* data = static_cast<char const*>(data_);
    buffers.push_back(std::vector<char>());
    buffers.back().assign(data, data + size);
}
